package com.openNetty.server.nettyserver;

import com.openNetty.common.api.BackSqlFile;
import com.openNetty.common.bean.ComRequest;
import com.openNetty.common.bean.ComResponse;
import com.openNetty.common.bean.HttpAck;
import com.openNetty.common.system.LoggerUtil;
import com.openNetty.common.utils.CRC8;
import com.openNetty.common.utils.MappedBiggerFileReader;
import com.openNetty.server.core.BackSqlFileImpl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

import static com.openNetty.common.bean.Command.*;
import static com.openNetty.common.bean.HttpInfo.*;
import static com.openNetty.common.bean.HttpInfo.KEY;
import static com.openNetty.common.bean.MainContents.*;
import static com.openNetty.common.utils.EncryptUtil.DESedeDecByte;
import static com.openNetty.common.utils.EncryptUtil.DESedeEncByte;
import static com.openNetty.common.utils.EncryptUtil.getKey;
import static com.openNetty.common.utils.MD5Util.getFileMD5;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;

/**
 * netty消息处理handler
 *
 * @author zhenggm
 * @create 2017-08-21 下午 1:36
 **/

public class NodeServerHandler extends ChannelInboundHandlerAdapter {
    private static LoggerUtil logger = LoggerUtil.getLogger(NodeServerHandler.class);
    private static MappedBiggerFileReader reader;
    private static ComResponse result = new ComResponse();
    private static Throwable throwable = new Throwable();
    private BackSqlFile backSqlFile = new BackSqlFileImpl();
    private static String key;
    private static String mdfive = "";
    private static int ackCount = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ComRequest rsmRequest = transToRequest(msg);
        if (rsmRequest == null) {
            exceptionCaught(ctx, throwable);
        }
        String cmd = rsmRequest.getMessage();
        if (ACK.equals(cmd)) {
            ackCount++;
            if (ackCount % 1000 == 0) {
                logger.debug("接受指令为:[" + cmd + "]");
                ackCount = 0;
            }
        } else {
            logger.debug("接受指令为:[" + cmd + "]");
        }
        switch (cmd) {
            case GET_SQL_BACKFILE:
                try {
                    result = backSqlFile.getSqlBackFile(rsmRequest);
                    logger.debug("开始传输，文件路径为：" + result.getFilePath());
                    reader = new MappedBiggerFileReader(result.getFilePath(), Integer.parseInt(Global.properties.getProperty(BUFFERSIZE)),
                            rsmRequest.getHttpAck().getCount(), rsmRequest.getHttpAck().getPostition());
                } catch (IOException e) {
                    result.setSuccess(false);
                    result.setResult(e.getMessage());
                }
                sendMsg(reader, ctx);
                break;
            case GET_BREAK_BACKFILE:
                try {
                    String filePath = Global.ROOT +"/"+ rsmRequest.getHttpAck().getFileName();
                    logger.debug("继续传输断点文件，文件路径为：" + filePath);
                    logger.debug("断点位置为[" + rsmRequest.getHttpAck().getCount() + "," + rsmRequest.getHttpAck().getPostition() + "]");
                    reader = new MappedBiggerFileReader(filePath, Integer.parseInt(Global.properties.getProperty(BUFFERSIZE)),
                            rsmRequest.getHttpAck().getCount(), rsmRequest.getHttpAck().getPostition());
                    result.setFilePath(filePath);
                    result.setSuccess(true);
                    result.setResult(SUCCESS);
                } catch (IOException e) {
                    result.setSuccess(false);
                    result.setResult(e.getMessage());
                }
                sendMsg(reader, ctx);
                break;
            case ACK:
                sendMsg(reader, ctx);
                break;
            default:
                logger.debug("指令无效");
                break;
        }
    }

    /**
     * 发送文件内容
     *
     * @param reader
     * @param ctx
     * @throws IOException
     */
    public void sendMsg(MappedBiggerFileReader reader, ChannelHandlerContext ctx) throws IOException {
        if (reader.read() != -1) {
            ctx.write(setFileResponse(reader.getArray(), reader.getCount(), reader.getPosition()));
            ctx.flush();
        } else {
            reader.close();
            logger.debug("发送结束");
            mdfive = getFileMD5(result.getFilePath());
            // 发送结束确认帧
            ctx.writeAndFlush(setHttpResponse(result));
            ctx.close();
            reader.close();
        }
    }

    /**
     * 创建httpresponse对象
     *
     * @param bt       内容数组
     * @param count    内存分片位置
     * @param position 分片内开始位置
     * @return
     * @throws IOException
     */
    public FullHttpResponse setFileResponse(byte[] bt, int count, int position) throws IOException {
        key = getKey(Global.keyMap);
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                HttpResponseStatus.OK, Unpooled.copiedBuffer(DESedeEncByte(Global.keyMap.get(key), bt)));
        response.headers().set(CONTENT_TYPE, "text/xml; charset=UTF-8");
        response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
        response.headers().set(FILEPOSITION, position);
        response.headers().set(MEMCOUNT, count);
        response.headers().set(CRC, Integer.toHexString(0x00ff & CRC8.calcCrc8(bt)));
        response.headers().set(KEY, key);
        response.headers().set(MD5, mdfive);
        return response;
    }

    /**
     * 组装HttpResponse对象进行Netty传输
     *
     * @param result
     * @return
     * @throws IOException
     */

    public FullHttpResponse setHttpResponse(ComResponse result) throws IOException {
        key = getKey(Global.keyMap);
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                HttpResponseStatus.OK, Unpooled.copiedBuffer(
                DESedeEncByte(Global.keyMap.get(key), result.getResult().getBytes(CharsetUtil.UTF_8))));
        response.headers().set(CONTENT_TYPE, "text/xml; charset=UTF-8");
        response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
        response.headers().set(FILEPOSITION, 0);
        response.headers().set(MEMCOUNT, 0);
        response.headers().set(CRC, Integer.toHexString(0x00ff & CRC8.calcCrc8(result.getResult().getBytes())));
        response.headers().set(KEY, key);
        response.headers().set(MD5, mdfive);
        return response;
    }


    /**
     * 对接收到的MSG进行HTTP解码
     *
     * @param msg Object
     * @return
     * @throws Exception
     */
    private ComRequest transToRequest(Object msg) throws Exception {
        try {
            FullHttpRequest request = (FullHttpRequest) msg;
            HttpAck ack = new HttpAck();
            // 获取请求头
            int count = Integer.parseInt(request.headers().get(MEMCOUNT));
            int position = Integer.parseInt(request.headers().get(FILEPOSITION));
            String filename = request.headers().get(FILENAME);
            key = request.headers().get(KEY);
            ack.setFileName(filename);
            ack.setCount(count);
            ack.setPostition(position);
            ack.setKey(key);
            // 获取请求内容体
            ByteBuf buf = request.content();
            HttpMethod method = request.method();
            Map<String, String> parmMap = new HashMap<>();
            Map<String, String> dataMap = new HashMap<>();
            byte[] req = null;

            String message = null;
            // 判断请求方式
            if (HttpMethod.GET == method) {
                QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
                decoder.parameters().entrySet().forEach(entry -> {
                    // entry.getValue()是一个List, 只取第一个元素
                    parmMap.put(entry.getKey(), entry.getValue().get(0));
                });
                req = new byte[buf.readableBytes()];
                buf.readBytes(req);
                req = DESedeDecByte(Global.keyMap.get(key), req);
                message = new String(req, "UTF-8");
            } else if (HttpMethod.POST == method) {
                QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
                decoder.parameters().entrySet().forEach(entry -> {
                    // entry.getValue()是一个List, 只取第一个元素
                    dataMap.put(entry.getKey(), entry.getValue().get(0));
                });
                req = new byte[buf.readableBytes()];
                req = DESedeDecByte(Global.keyMap.get(key), req);
                buf.readBytes(req);
                String data = new String(req, "UTF-8");
                message = dataMap.get("command");
                parmMap.put("data", data);
            }
            ComRequest rq = new ComRequest();
            rq.setMessage(message);
            rq.setMap(parmMap);
            rq.setHttpAck(ack);
            return rq;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("transToRequest error -> " + e);
            throwable.initCause(e.getCause());
        }
        return null;
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.debug("通信中断，等待中");
        logger.debug(cause.getMessage());
        ctx.close();
    }

    /*
   * channelInactive
   *
   * 当客户端主动断开服务端的链接后，这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
   *
   */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.debug(ctx.channel().localAddress().toString() + " channelInactive");
        ctx.close();
    }
}